﻿using System;
using System.Text;

namespace ZProto
{
    public class BuffWriter
    {
        private byte[][] _buffs;
        private ushort[] _lengths;
        private byte _pageCount;
        private int _index;
        private ushort _page;
        private ushort _buffSize;
        private int _length;
        public BuffWriter(ushort buffSize = ushort.MaxValue, byte buffCount = 1)
        {
            _buffSize = buffSize;
            _pageCount = buffCount;
            _buffs = new byte[buffCount][];
            _lengths = new ushort[buffCount];
            for (var i = 0; i < buffCount; ++i)
            {
                _buffs[i] = new byte[buffSize];
            }
            _index = 0;
            _page = 0;
        }
        public void Reset()
        {
            _page = 0;
            _index = 0;
            _length = 0;
        }
        public byte[] ToBytes()
        {
            _length += _index;
            _lengths[_page] = (ushort)_index;
            var buff = new byte[_length];
            int size = 0;
            for (var i = 0; i <= _page; i++)
            {
                var length = _lengths[i];
                for (var j = 0; j < length; j++)
                {
                    buff[size++] = _buffs[i][j];
                }
            }
            return buff;
        }
        public void ForPage(Action<byte[], int> action)
        {
            for (var i = 0; i <= _page; i++)
            {
                action(_buffs[i], _lengths[i]);
            }
        }
        private byte[] GetBuff(int size)
        {
            byte[] buff = _buffs[_page];
            if (_index + size > _buffSize)
            {
                var length = _index;
                _lengths[_page] = (ushort)(length);
                _length += length;
                _page++;
                _index = 0;
                if (_page >= _pageCount)
                {
                    var count = _pageCount + 1;
                    var buffs = new byte[count][];
                    var lengths = new ushort[count];
                    for (var i = 0; i < _pageCount; ++i)
                    {
                        buffs[i] = _buffs[i];
                        lengths[i] = _lengths[i];
                    }
                    _pageCount++;
                    buffs[_page] = new byte[_buffSize];
                    _buffs = buffs;
                    _lengths = lengths;
                    buff = buffs[_page];
                }
                else
                {
                    buff = _buffs[_page];
                }
            }
            return buff;
        }
        public unsafe void _bool(bool value)
        {
            var buff = GetBuff(sizeof(bool));
            fixed (byte* ptr = buff)
            {
                *(bool*)(ptr + _index) = value;
                _index += sizeof(bool);
            }
        }
        public unsafe void _byte(byte value)
        {
            var buff = GetBuff(1);
            buff[_index++] = value;
        }
        public void _sbyte(sbyte value)
        {
            _byte((byte)value);
        }
        public void _short(short value)
        {
            _ushort((ushort)value);
        }
        public unsafe void _ushort(ushort value)
        {
            var size = sizeof(ushort);
            var buff = GetBuff(size);
            fixed (byte* ptr = buff)
            {
                *(ushort*)(ptr + _index) = Utils.Endian(value);
                _index += size;
            }
        }
        public void _int(int value)
        {
            _uint((uint)value);
        }
        public unsafe void _uint(uint value)
        {
            var size = sizeof(uint);
            var buff = GetBuff(size);
            fixed (byte* ptr = buff)
            {
                *(uint*)(ptr + _index) = Utils.Endian(value);
                _index += size;
            }
        }
        public void _long(long value)
        {
            _ulong((ulong)value);
        }
        public unsafe void _ulong(ulong value)
        {
            var size = sizeof(ulong);
            var buff = GetBuff(size);
            fixed (byte* ptr = buff)
            {
                *(ulong*)(ptr + _index) = Utils.Endian(value);
                _index += size;
            }
        }
        public void _float(float value)
        {
            _uint(Utils.F2UI(value));
        }
        public void _double(double value)
        {
            _ulong(Utils.D2UL(value));
        }
        public void _string(string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                _ushort((ushort)0);
                return;
            }
            var bytes = Encoding.UTF8.GetBytes(value);
            _ushort((ushort)bytes.Length);
            for (int i = 0; i < bytes.Length; i++)
            {
                _byte(bytes[i]);
            }
        }
        public void _array<T>(T[] values, Action<T> write)
        {
            if (values == null || values.Length == 0)
            {
                _uint(0u);
                return;
            }
            _uint((uint)values.Length);
            for (var i = 0; i < values.Length; ++i)
            {
                write(values[i]);
            }
        }
    }
}
